import urllib.request
import json
import ssl
import xvod_client.xos_signer as xos_signer
from urllib.error import HTTPError, URLError
from datetime import datetime
from typing import Tuple
from .logger import get_logger
from .errors import XVODAPIError

DEFAULT_TIMEOUT = 300
DEFAULT_RETRY_TIMES = 3


class HttpClient(object):

    def __init__(self, endpoint, access_key, secret_key, timeout: int = DEFAULT_TIMEOUT,
                 retry_times: int = DEFAULT_RETRY_TIMES, unsecure: bool = False) -> None:
        self.endpoint = endpoint
        self.signer = xos_signer.XoSigner(access_key, secret_key)
        self.timeout = timeout
        self.retry_times = retry_times
        self.ssl_ctx = ssl.create_default_context()
        if unsecure:
            self.ssl_ctx.check_hostname = False
            self.ssl_ctx.verify_mode = ssl.CERT_NONE

    def _urlopen(self, req: urllib.request.Request, data: bytes = None) -> Tuple[dict, str]:
        """
        " Return (headers, body)
        """

        with urllib.request.urlopen(req, data=data, timeout=self.timeout, context=self.ssl_ctx) as res:
            return res.headers, res.read().decode('utf-8')

    def _urlopen_with_retry(self, req: urllib.request.Request, data: bytes = None) -> Tuple[dict, str]:
        retry_times = self.retry_times
        while True:
            try:
                return self._urlopen(req, data)
            except HTTPError as e:
                if e.code in (408, 429, 499, 500, 502, 503, 504):
                    if retry_times <= 0:
                        get_logger().warning(f"Status {e.code}, retry times {retry_times} left, raise")
                        raise
                    else:
                        get_logger().warning(f"Status {e.code}, retry times {retry_times} left, retry")
                        retry_times -= 1
                        continue
                else:
                    get_logger().debug(f"raise error {e}")
                    raise
            except URLError as e:
                if isinstance(e.reason, TimeoutError):
                    if retry_times <= 0:
                        get_logger().warning(f"TimeoutError, retry times {retry_times} left, raise")
                        raise
                    else:
                        get_logger().warning(f"TimeoutError, retry times {retry_times} left, retry")
                        retry_times -= 1
                        continue
                else:
                    raise

    def post(self, uri: str, data: dict = {}) -> dict:
        date = datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
        _headers = {
            'Content-Type': 'application/json',
            'Date': date,
            'signature': self.signer.authorization_signature(date, 'POST', uri)
        }
        url = self.endpoint + uri
        req = urllib.request.Request(url, headers=_headers, method="POST")
        _, body = self._urlopen_with_retry(req, json.dumps(data).encode('utf-8'))
        if not body:
            return {}
        try:
            result = json.loads(body)
            if result.get('code') != '0':
                get_logger().error(f"POST error, message: {result.get('message')}")
                raise XVODAPIError(result.get('code'), result.get('message'))
            return result
        except json.decoder.JSONDecodeError as e:
            get_logger().error(f"Failed to parse body with error[{e}]: {body}")
            raise

    def put(self, url: str, data: bytes, headers: dict = {}, result_from_body: bool = False) -> dict:
        req = urllib.request.Request(url, headers=headers, method="PUT")
        headers, body = self._urlopen_with_retry(req, data)
        if not result_from_body:
            return headers
        if body:
            try:
                return json.loads(body)
            except Exception as e:
                get_logger().error(e)
        else:
            return {}
